﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using NUnit.Framework;

namespace Henke.DevUI.TestUtils.Common
{
    public class ListenerForPropertyChanged : IPropertyOwner, IVerifyOnDispose
    {
        private readonly IList<string> _receivedChangedProperties = new List<string>();
        private readonly IList<string> _propertiesToListenFor = new List<string>();

        private INotifyPropertyChanged _target;

        public IPropertyOwner ListenTo(INotifyPropertyChanged target)
        {

            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            _target = target;
            _target.PropertyChanged += OnPropertyChanged;
            return this;
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            _receivedChangedProperties.Add(e.PropertyName);
        }

        public IVerifyOnDispose For(string property)
        {
            _propertiesToListenFor.Add(property);
            return this;
        }

        public void Dispose()
        {
            if (_target != null)
            {
                _target.PropertyChanged -= OnPropertyChanged;

                foreach (var propertyName in _propertiesToListenFor)
                {
                    Assert.IsTrue(_receivedChangedProperties.Contains(propertyName), string.Format("PropertyChanged for {0} was never fired", propertyName));
                }
            }
            GC.SuppressFinalize(this);
        }

        public IVerifyOnDispose For<TProperty>(Expression<Func<TProperty>> property)
        {
            var lambda = (LambdaExpression)property;
            MemberExpression memberExpression;

            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = (UnaryExpression)lambda.Body;
                memberExpression = (MemberExpression)unaryExpression.Operand;
            }
            else
            {
                memberExpression = (MemberExpression)lambda.Body;
            }

            return For(memberExpression.Member.Name);
        }


        public IVerifyOnDispose And(string property)
        {
            return For(property);
        }

        public IVerifyOnDispose And<TProperty>(Expression<Func<TProperty>> property)
        {
            return For(property);
        }
    }

    public interface IVerifyOnDispose : IDisposable
    {
        IVerifyOnDispose And(string property);

        IVerifyOnDispose And<TProperty>(Expression<Func<TProperty>> property);
    }

    public interface IPropertyOwner
    {
        IVerifyOnDispose For(string property);

        IVerifyOnDispose For<TProperty>(Expression<Func<TProperty>> property);
    }
}
